﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Infrastructure.DataTypes;

namespace Infrastructure
{ 
    public class DataSet<R> where R : ICountable,new()//Data set now only for Icountable , change it in future + average implementation
    {
        private Dictionary<string, User<R>> users;//users data with all user attributes
        private Dictionary<string, Item<R>> items;//item data with all items attrbutes
        private Random rand;//rand for singeltone use

        public DataSet()
        {
            users = new Dictionary<string, User<R>>();
            items = new Dictionary<string, Item<R>>();
            rand = new Random();
        }

        /// <summary>
        /// get all usres of data set
        /// </summary>
        public Dictionary<string, User<R>> Users 
        {
            get { return users; } 
        }
        /// <summary>
        /// get all items of data set
        /// </summary>
        public Dictionary<string, Item<R>> Items
        {
            get { return items; }
        }


        #region add data to data set
        /// <summary>
        /// adding new user to data set
        /// </summary>
        /// <param name="user">Object from User type</param>
        public void addUser(User<R> user)
        {
            if (!users.ContainsKey(user.UserID))
            {
                users.Add(user.UserID, user);
            }

        }

        /// <summary>
        /// adding new item to data set
        /// </summary>
        /// <param name="item"></param>
        public void addItem(Item<R> item)
        {
            if (!items.ContainsKey(item.ItemID))
            {
                items.Add(item.ItemID, item);
            }
        }


        /// <summary>
        /// add ratting to data set for specific user to current item
        /// </summary>
        /// <param name="user">User object</param>
        /// <param name="item">Item object</param>
        /// <param name="rating">Rating that user gave to item</param>
        public void addUserItemRating(User<R> user, Item<R> item, R rating)
        {
            string uID = user.UserID;
            string sID = item.ItemID;
            if (!users.ContainsKey(uID))
            {
                users.Add(uID, user);
            }
            if (!items.ContainsKey(sID))
            {
                items.Add(sID, item);
            }
            users[uID].AddScore(items[sID], rating);
            items[sID].AddScore(users[uID], rating);

            Calculations( rating, uID, sID, users[uID].Votes.Count);//change user statis after adding new rate


        }
        
        /// <summary>
        /// generate random user and update private field picked user
        /// in order to get it use metod getRandomUser
        /// </summary>
        
        #endregion

        #region private methodes
        /// <summary>
        /// Calculetes all the statistics
        /// </summary>
        /// <param name="sum"></param>
        /// <param name="avg"></param>
        /// <param name="rating"></param>
        /// <param name="uID"></param>
        /// <param name="sID"></param>
        private void Calculations( R rating, string uID, string sID, int counter)
        {
            double atr;
            if (/*rating is Descreet || rating is Continuous ||*/ rating is ICountable)
            {
                //user statistic calculation
                atr = users[uID].StatisticsAttributes.RatingSum;
                if (atr != 0)
                {
                    double valueSum = atr + rating;
                    users[uID].StatisticsAttributes.RatingSum = valueSum;
                    double valueAvg = valueSum / counter;
                    users[uID].StatisticsAttributes.AverageRating = valueAvg;
                }
                else
                {
                    double value = rating;
                    users[uID].StatisticsAttributes.RatingSum = value;
                    users[uID].StatisticsAttributes.AverageRating = value;
                }
                double counterV = counter;
                users[uID].StatisticsAttributes.VoteCount = counterV;
                //item statistic calculation


                counter = items[sID].Scores.Count;
                atr = items[sID].StatisticsAttributes.RatingSum;
                if (atr != 0)
                {
                    double valueSum = atr + rating;
                    items[sID].StatisticsAttributes.RatingSum = valueSum;
                    double valueAvg = valueSum / counter;
                    items[sID].StatisticsAttributes.AverageRating = valueAvg;
                }
                else
                {
                    double value = rating;
                    items[sID].StatisticsAttributes.RatingSum = value;
                    items[sID].StatisticsAttributes.AverageRating = value;
                }



                items[sID].StatisticsAttributes.VoteCount = counter;
            }

        }


        #endregion

        /// <summary>
        /// return Top n most voted Items in data set
        /// </summary>
        /// <returns>List of itens</returns>
        public List<Item<R>> getSortedItemsByRating(int n)
        {   
            Item<R>.CompareByAVG comp = new Item<R>.CompareByAVG();//create comparer
            List<Item<R>> itms = items.Values.ToList();//get the Items themself from dictionary of all items

            //sorting list by highest prediction.
            var sortedCounts = from entry in itms                                
                               orderby entry.StatisticsAttributes.VoteCount
                               descending select entry;

          //  itms.Sort(comp);//sort them when the items compered by comparer
          //  itms.Reverse();//revers the order of them
            List<Item<R>> itm = sortedCounts.Take(n).ToList<Item<R>>();//take onli 10 the most popular Item

            return itm;
        }

        /// <summary>
        /// getting items that not rated by picked user
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Item<R>> getItemsNotRatedByUser(User<R> user)
        {
            
            List<Item<R>>ans = new List<Item<R>>();
            foreach (string item in items.Keys)
            {
                if (!users[user.UserID].Votes.ContainsKey(items[item]))
                {
                    ans.Add(items[item]);
                }
            }
            return ans; 
        }

        /// <summary>
        /// return ture if user with userID exist in data set
        /// </summary>
        /// <param name="userID"></param>
        /// <returns></returns>
        public bool hasUser(string userID)
        {
            return users.ContainsKey(userID);
        }

        /// <summary>
        /// return ture if user with userID exist in data set
        /// </summary>
        /// <param name="userID"></param>
        /// <returns></returns>
        public bool hasUser(User<R> user)
        {
            return users.ContainsKey(user.UserID);
        }
        /// <summary>
        /// return ture if item with itemID exist in data set
        /// </summary>
        /// <param name="userID"></param>
        /// <returns></returns>
        public bool hasItem(string itemID)
        {
            return items.ContainsKey(itemID);
        }
        /// <summary>
        /// return ture if item with itemID exist in data set
        /// </summary>
        /// <param name="userID"></param>
        /// <returns></returns>
        public bool hasItem(Item<R> item)
        {
            return items.ContainsKey(item.ItemID);
        }

        /// <summary>
        /// return rateing that user with ID=uID gave to item with item ID=sID
        /// if item or user not exists in data set throw exception
        /// </summary>
        /// <param name="uID">user ID</param>
        /// <param name="sID">iten ID</param>
        /// <returns>rating</returns>
        public R getRating(string uID, string sID)
        {
            try
            {

                return users[uID].Votes[items[sID]];

            }
            catch (Exception ex)
            {

                throw ex;
            }
        }

        /// <summary>
        /// return rateing that user with ID=uID gave to item with item ID=sID
        /// if item or user not exists in data set throw exception
        /// </summary>
        /// <param name="uID">user ID</param>
        /// <param name="sID">iten ID</param>
        /// <returns>rating</returns>
        public R getRating(User<R> uID, Item<R> sID)
        {
            try
            {
                return uID.Votes[sID];

            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
        /// <summary>
        /// check if user rated item if yes return true else now
        /// </summary>
        /// <param name="uID">user ID</param>
        /// <param name="sID">Item ID</param>
        /// <returns></returns>
        public bool hasRating(string uID, string sID)
        {
            try
            {
                if (users.ContainsKey(uID) && users[uID].Votes.ContainsKey(items[sID]))
                {
                    return true;
                }
                return false;
            }
            catch (Exception ex)
            {
                throw ex;

            }

        }
        /// <summary>
        /// get all users  that exists in data set
        /// </summary>
        /// <returns>IEnumerable with user IDs</returns>
        public IEnumerable<string> getAllUsersIds()
        {
            return users.Keys.ToList<string>();
        }

        /// <summary>
        /// chose randomaly one user from list of users
        /// </summary>
        /// <returns></returns>
        public User<R> GenerateRandomUser()
        {
            List<string> temp_array = users.Keys.ToList<string>();
            int numberOfUsers = users.Count;
            int randPlace = -1;
            string picked_user;
            int counter = numberOfUsers;//limit the random user getting in order to prevent round bound exception
            do
            {
                counter--;
                randPlace = rand.Next(0, numberOfUsers);
                picked_user = temp_array[randPlace];

            } while (users[picked_user].Votes.Count <= 3 && counter != 0);
            if (users[picked_user].Votes.Count <= 3)
            {
                picked_user = null;
            }
            return users[picked_user];
        }
        /// <summary>
        /// get list of N users that picked randomaly from data set
        /// </summary>
        /// <param name="amountOfWantedUsID">amount of users that you want</param>
        /// <returns>List of strings</returns>
        public List<User<R>> getListOfRandomUsers(int amountOfWantedUsID)
        {
            List<User<R>> ans = new List<User<R>>();
            while (ans.Count < amountOfWantedUsID)
            {
                User<R> picked_user = GenerateRandomUser();
                if (picked_user!=null && !ans.Contains(picked_user))
                {
                    ans.Add(picked_user);
                }
            }
           
            return ans;
        }

        public Item<R> getItem(string sID)
        {
            if (items.ContainsKey(sID))
            {
                return items[sID];
            }
            else
            {
                return null;
            }
        }
        /// <summary>
        /// generate user with new random id and adding to dataset
        /// </summary>
        /// <returns></returns>
        public User<R> generateUseID()
        {
            int counter = users.Count + 1;//limit the random user getting in order to prevent round bound exception
            int randPlace;
            do
            {
                counter++;
                randPlace = rand.Next(counter,1000000);
            } while (users.ContainsKey(randPlace.ToString()));
            User<R> newUser = new User<R>(randPlace.ToString());
            users.Add(randPlace.ToString(), newUser);
            return newUser;
        }
        
    }
}
